﻿/***************************************************************
 * 开发人员：网魂小兵@http://www.mozlite.com
 * 
 * 缓存管理类，可以在cache.config文件中设定缓存数据提供者。
 * 
 * 最后修改：2011-10-28
 * ************************************************************/
namespace Mozlite.Caching
{
    using System;
    using System.Web.Caching;
    using System.Web;
    using System.IO;
    using System.Collections.Generic;
    using System.Collections;
    using System.Configuration;
    using Mozlite.Data;

    /// <summary>
    /// 缓存管理类，可以在cache.config文件中设定缓存数据提供者。
    /// </summary>
    public class CacheStore
    {
        private static volatile CacheProviderBase cacheProvider;
        private static readonly string defaultProviderName = "HttpRuntimeCacheProvider";
        private static readonly object locker = new object();
        /// <summary>
        /// 设置无绝对过期时间。
        /// </summary>
        public static readonly DateTime NoAbsoluteExpiration = Cache.NoAbsoluteExpiration;
        /// <summary>
        /// 设置无相对过期时间。
        /// </summary>
        public static readonly TimeSpan NoSlidingExpiration = Cache.NoSlidingExpiration;

        static CacheStore()
        {
            InternalInitialize();
        }

        private CacheStore()
        {
        }
        /// <summary>
        /// 清楚所有缓存。
        /// </summary>
        public static void Clear()
        {
            CacheProvider.Clear();
        }

        private static void ConfigurationAltered()
        {
            HttpRuntime.Cache.Insert("Mozlite.Caching.CacheStore::caching.config::refresh", true, null, DateTime.UtcNow, Cache.NoSlidingExpiration, CacheItemPriority.Normal, delegate(string key, object value, CacheItemRemovedReason reason)
            {
                CacheConfigurationSection.Refresh();
                InternalInitialize();
            });
        }

        private static CacheProviderBase CreateProvider(string providerName = null)
        {
            CacheConfigurationSection current = CacheConfigurationSection.Current;
            if (current == null)
            {
                throw new ProviderNotFoundException("providerName");
            }
            providerName = string.IsNullOrEmpty(providerName) ? current.DefaultProvider : providerName;
            if (providerName == defaultProviderName)
            {
                return new HttpRuntimeCacheProvider();
            }
            ProviderSettingsCollection providers = current.Providers;
            if (providers == null)
            {
                throw new ProviderConfigurationEmptyException();
            }
            ProviderSettings settings = providers[providerName];
            if (settings == null)
            {
                throw new ProviderNotFoundException("providerName", providerName);
            }
            return CreateProvider(settings.Name, settings.Type);
        }
        /// <summary>
        /// 通过提供的提供者名称和类型创建实例对象。
        /// </summary>
        /// <param name="name">名称。</param>
        /// <param name="type">类型。</param>
        /// <returns>返回缓存提供者实例对象。</returns>
        public static CacheProviderBase CreateProvider(string name, string type)
        {
            return CacheStoreProvider<CacheProviderBase>.CreateProvider(name, type);
        }
        /// <summary>
        /// 获取缓存键中的所有缓存列表。
        /// </summary>
        /// <typeparam name="T">缓存对象类型。</typeparam>
        /// <param name="keys">缓存键迭代器实例对象。</param>
        /// <returns>返回缓存列表。</returns>
        public static IDictionary<string, T> Get<T>(IEnumerable<string> keys)
        {
            return CacheProvider.Get<T>(keys);
        }
        /// <summary>
        /// 获取当前键的缓存对象。
        /// </summary>
        /// <param name="key">缓存键。</param>
        /// <returns>返回缓存对象。</returns>
        public static object Get(string key)
        {
            return CacheProvider.Get(key);
        }
        /// <summary>
        /// 获取当前键的缓存对象。
        /// </summary>
        /// <typeparam name="T">缓存类型。</typeparam>
        /// <param name="key">缓存键。</param>
        /// <param name="loader">如果缓存为空，则加载器加载内容。</param>
        /// <returns>返回缓存对象。</returns>
        public static T Get<T>(string key, CacheLoaderDelegate loader)
        {
            T value = CacheProvider.Get<T>(key);
            if (value == null)
                value = (T)loader();
            return value;
        }
        /// <summary>
        /// 获取枚举非泛型字典实例对象。
        /// </summary>
        /// <returns>返回枚举非泛型字典实例对象。</returns>
        public static IDictionaryEnumerator GetEnumerator()
        {
            return CacheProvider.GetEnumerator();
        }
        /// <summary>
        /// 将所有字典中的缓存项插入到缓存中。
        /// </summary>
        /// <typeparam name="T">缓存类型。</typeparam>
        /// <param name="items">缓存列表实例对象。</param>
        /// <param name="dependencies">缓存依赖项。</param>
        /// <param name="absoluteExpiration">缓存过期绝对日期。</param>
        /// <param name="slidingExpiration">缓存过期相对日期。</param>
        /// <param name="priority">缓存优先级。</param>
        /// <param name="onRemoveCallback">单移除缓存触发的回调。</param>
        public static void Insert<T>(IDictionary<string, T> items, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration, CacheItemPriority priority, CacheItemRemovedCallback onRemoveCallback)
        {
            CacheProvider.Insert<T>(items, dependencies, absoluteExpiration, slidingExpiration, priority, onRemoveCallback);
        }
        /// <summary>
        /// 将对象插入到缓存中。
        /// </summary>
        /// <param name="key">缓存键。</param>
        /// <param name="value">缓存对象。</param>
        /// <param name="dependencies">缓存依赖项。</param>
        /// <param name="absoluteExpiration">缓存过期绝对日期。</param>
        /// <param name="slidingExpiration">缓存过期相对日期。</param>
        /// <param name="priority">缓存优先级。</param>
        /// <param name="onRemoveCallback">单移除缓存触发的回调。</param>
        public static void Insert(string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration, CacheItemPriority priority, CacheItemRemovedCallback onRemoveCallback)
        {
            CacheProvider.Insert(key, value, dependencies, absoluteExpiration, slidingExpiration, priority, onRemoveCallback);
        }

        internal static object InternalCallback(string key)
        {
            CacheEntry cacheEntry = CacheLockbox.GetCacheEntry(key);
            if (cacheEntry == null)
            {
                return null;
            }
            CacheLoaderDelegate cacheLoader = cacheEntry.CacheLoader;
            if (cacheLoader == null)
            {
                return null;
            }
            object obj = null;
            try
            {
                obj = cacheLoader();
            }
            catch (Exception exception)
            {
                Cacher.HandleCacheLoaderError(key, exception);
            }
            if (obj != null)
            {
                DateTime now = DateTime.UtcNow;
                TimeSpan span = (TimeSpan)(DateTime.MaxValue - now);
                DateTime absoluteExpiration = (TimeSpan.Compare(span, cacheEntry.RefreshInterval) == 1) ? now.Add(cacheEntry.RefreshInterval) : DateTime.MaxValue;
                Insert(key, obj, null, absoluteExpiration, NoSlidingExpiration, cacheEntry.CacheItemPriority, null);
                CacheLockbox.UpdateCacheEntryLastUpdate(key, now);
            }
            return obj;
        }

        private static void InternalInitialize()
        {
            lock (locker)
            {
                CacheProviderBase pbase = cacheProvider;
                cacheProvider = CreateProvider();
                if (pbase != null)
                {
                    pbase.Dispose();
                }
                SetupFileWatcher();
            }
        }
        /// <summary>
        /// 移除缓存。
        /// </summary>
        /// <param name="key">缓存键。</param>
        public static void Remove(string key)
        {
            CacheProvider.Remove(key);
        }
        /// <summary>
        /// 重新设定缓存提供者。
        /// </summary>
        /// <param name="providerName">缓存提供者名称。</param>
        public static void RetaskProvider(string providerName)
        {
            lock (locker)
            {
                cacheProvider = CreateProvider(providerName);
            }
        }
        /// <summary>
        /// 重新设定缓存提哦能够在。
        /// </summary>
        /// <param name="name">缓存提供者名称。</param>
        /// <param name="type">类型字符串。</param>
        public static void RetaskProvider(string name, string type)
        {
            lock (locker)
            {
                cacheProvider = CreateProvider(name, type);
            }
        }

        private static void SetupFileWatcher()
        {
            string cachingConfigurationFilePath = CacheConfigurationSection.CachingConfigurationFilePath;
            if (!string.IsNullOrEmpty(cachingConfigurationFilePath) && File.Exists(cachingConfigurationFilePath))
            {
                CacheDependency dependencies = new CacheDependency(cachingConfigurationFilePath);
                HttpRuntime.Cache.Insert("Mozlite.Caching.CacheStore::caching.config::watch", true, dependencies, DateTime.MaxValue, Cache.NoSlidingExpiration, CacheItemPriority.High, delegate(string key, object value, CacheItemRemovedReason reason)
                {
                    ConfigurationAltered();
                });
            }
        }
        /// <summary>
        /// 更新缓存项。
        /// </summary>
        /// <param name="key">更新缓存项。</param>
        public static void Update(string key)
        {
            if (CacheLockbox.ContainsCacheEntry(key))
            {
                lock (CacheLockbox.GetLock(key))
                {
                    InternalCallback(key);
                }
            }
        }

        private static CacheProviderBase CacheProvider
        {
            get
            {
                lock (locker)
                {
                    return cacheProvider;
                }
            }
        }
        /// <summary>
        /// 获取缓存提供者名称。
        /// </summary>
        public static string ProviderName
        {
            get
            {
                return CacheProvider.ProviderName;
            }
        }

        /// <summary>
        /// 获取缓存对象。
        /// </summary>
        /// <typeparam name="T">缓存类型。</typeparam>
        /// <param name="key">缓存键。</param>
        /// <returns>返回缓存实例对象。</returns>
        /// <remarks>使用此方法需要注意，必须已经确定缓存中具有该键的缓存项，否则将抛出错误！</remarks>
        public static T Get<T>(string key)
        {
            if (!CacheLockbox.ContainsCacheEntry(key))
            {
                throw new Exception("The cache entry of " + key + " is not found!");
            }
            return GetInternal<T>(true, key, TimeSpan.Zero, TimeSpan.Zero, null, CacheItemPriority.Default);
        }
        /// <summary>
        /// 获取缓存实例对象。
        /// </summary>
        /// <typeparam name="T">缓存类型。</typeparam>
        /// <param name="key">缓存键。</param>
        /// <param name="refreshIntervalSeconds">缓存刷新时间。</param>
        /// <param name="slidingExpirationSeconds">缓存相对过期时间。</param>
        /// <param name="loaderDelegate">缓存加载代理方法。</param>
        /// <param name="priority">缓存优先级。</param>
        /// <returns>返回缓存实例对象。</returns>
        public static T Get<T>(string key, int refreshIntervalSeconds, int slidingExpirationSeconds, CacheLoaderDelegate loaderDelegate, CacheItemPriority priority = CacheItemPriority.Default)
        {
            return Get<T>(key, TimeSpan.FromSeconds((double)refreshIntervalSeconds), TimeSpan.FromSeconds((double)slidingExpirationSeconds), loaderDelegate, priority);
        }
        /// <summary>
        /// 获取缓存实例对象。
        /// </summary>
        /// <typeparam name="T">缓存类型。</typeparam>
        /// <param name="key">缓存键。</param>
        /// <param name="refreshInterval">缓存刷新时间。</param>
        /// <param name="slidingExpiration">缓存相对过期时间。</param>
        /// <param name="loaderDelegate">缓存加载代理方法。</param>
        /// <param name="priority">缓存优先级。</param>
        /// <returns>返回缓存实例对象。</returns>
        public static T Get<T>(string key, TimeSpan refreshInterval, TimeSpan slidingExpiration, CacheLoaderDelegate loaderDelegate, CacheItemPriority priority = CacheItemPriority.Default)
        {
            return GetInternal<T>(false, key, refreshInterval, slidingExpiration, loaderDelegate, priority);
        }

        private static T GetInternal<T>(bool keyOnly, string key, TimeSpan refreshInterval, TimeSpan slidingExpiration, CacheLoaderDelegate loaderDelegate, CacheItemPriority priority)
        {
            object value = CacheStore.Get(key);
            if (IsObjectNotT<T>(value))
            {
                object locker = !keyOnly ? CacheLockbox.GetLock(key, refreshInterval, slidingExpiration, loaderDelegate, priority) : CacheLockbox.GetLock(key);
                lock (locker)
                {
                    value = CacheStore.Get(key);
                    if (IsObjectNotT<T>(value))
                    {
                        value = CacheStore.InternalCallback(key);
                    }
                }
            }
            else
            {
                CacheLockbox.UpdateCacheEntryLastUse(key, DateTime.UtcNow);
            }
            if (IsObjectT<T>(value))
            {
                return (T)value;
            }
            return default(T);
        }

        private static bool IsObjectNotT<TType>(object o)
        {
            return ((o == null) || !(o is TType));
        }

        private static bool IsObjectT<TType>(object o)
        {
            return ((o != null) && (o is TType));
        }

        #region helpers
        private class CacheStoreProvider<T> where T : CacheProviderBase
        {
            private static readonly object locker = new object();
            private static string defaultName = null;
            private static T defaultType = null;
            private static readonly Dictionary<string, CacheProviderBase> providers = new Dictionary<string, CacheProviderBase>(StringComparer.OrdinalIgnoreCase);

            private static T CreateInstance(string providerName, string providerType)
            {
                Type type = Type.GetType(providerType);
                if (type == null)
                    throw new Exception("Cannot get the type of " + providerType);
                T value = (T)Activator.CreateInstance(type);
                if (value == null)
                    throw new Exception("The type of " + providerType + " cannot be initialized!");
                return value;
            }

            public static T CreateProvider(ProviderSettings settings)
            {
                if (settings == null)
                    throw new ArgumentNullException("settings");
                return CreateInstance(settings.Name, settings.Type);
            }

            public static T CreateProvider(string providerName, string providerType)
            {
                if (string.IsNullOrEmpty(providerName))
                    throw new ArgumentNullException("providerName");
                if (string.IsNullOrEmpty(providerType))
                    throw new ArgumentNullException("providerType");
                lock (CacheStore.CacheStoreProvider<T>.locker)
                {
                    if (defaultName == null)
                    {
                        defaultName = providerName;
                        defaultType = CreateInstance(providerName, providerType);
                        return defaultType;
                    }
                    if (defaultName.IsMatched(providerName))
                        return defaultType;
                    CacheProviderBase value;
                    if (!providers.TryGetValue(providerName, out value))
                    {
                        value = CreateInstance(providerName, providerType);
                        providers.Add(providerName, value);
                    }
                    return (T)value;
                }
            }
        }
        #endregion
    }
}
